pytest-given
pytest 9.0.3 · pytest-given 0.1.0 ·
Term:
A scenario records under its node ID happy-path
✓ passed 0ms
Given
a fresh Collector
When
a Scenario starts under its Node ID and finishes
Then
it carries its Node ID, name, status, duration and Tag
Steps record with their phases happy-path
✓ passed 0ms
Given
an Active scenario in a fresh Collector
When
a given and a when Step are pushed
Then
each Step carries its Phase
Steps pushed during fixture setup record into the fixture recording happy-path
✓ passed 0ms
Given
a Fixture recording under setup
When
a Step is pushed inside the fixture body
Then
it is recorded as a child of the recording root
An attachment lands on the step being recorded happy-path
✓ passed 0ms
Given
a Fixture recording under setup
When
an Attachment is attached inside the fixture body
Then
the Attachment lands on the recording root
Fixture-body steps do not leak into the active scenario happy-path
✓ passed 0ms
Given
an Active scenario with a Fixture recording
When
a Step is pushed inside the fixture body
Then
the step lives only in the recording, not the scenario
A fixture recording is deep-copied when grafted happy-path
✓ passed 0ms
Given
a Fixture recording with a nested child Step
When
a Graft copies it into the Active scenario
Then
the scenario gains a deep copy of the recorded steps
A leaf given is grafted as a childless given step happy-path
✓ passed 0ms
Given
an Active scenario is being recorded
When
a leaf Graft appends a childless Step
Then
the step is a given with no children
Grafting with an override replaces the root label but keeps children happy-path
✓ passed 0ms
Given
a Fixture recording whose root has a label and a child
When
a Graft supplies an override Narration
Then
the grafted root shows the override text and keeps its children
Grafting with no active scenario is a no-op happy-path
✓ passed 0ms
Given
a collector with no Active scenario
When
a leaf Graft runs
Then
no scenario is recorded
FileGlossary lookup is case-insensitive happy-path
✓ passed 1ms
Given
a File glossary loaded from a Markdown file
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
the same Term is looked up in three different cases
Then
every lookup resolves to one handle type and the same id
Repeated lookups return the same handle happy-path
✓ passed 0ms
Given
a File glossary loaded from a Markdown file
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
the same Term is looked up twice
Then
both lookups return the one memoized handle
File-loaded terms start kindless happy-path
✓ passed 1ms
Given
a Markdown glossary file with no kind column
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
a File glossary loads it
Then
each Term is Kindless until Kind inference runs
An unknown name raises with a suggestion validation
✓ passed 1ms
Given
a File glossary loaded from a Markdown file
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
a misspelt Term is looked up
Then
a PytestGivenError is raised with a spelling hint
Handles are usable inline in an activity story-grammar
✓ passed 0ms
Given
a File glossary loaded from a Markdown file
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
its handles build an Activity
Then
each slot becomes a Term ref
Calling a handle overrides its display story-grammar
✓ passed 1ms
Given
a File glossary loaded from a Markdown file
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
a handle is called to name an Instance
Then
the Term ref carries the overridden display
An explicit kind column sets term kinds happy-path
✓ passed 1ms
Given
a Markdown glossary with an explicit Kind column
Glossary file
| Term | Meaning | Kind |
|---|---|---|
| Guest | x | Actor |
| Room | y | Work Object |
When
the File glossary reads the Kind column
Then
kinds come straight from the file, not Kindless inference
A kind column can be selected by integer index happy-path
✓ passed 1ms
Given
a Markdown glossary with the kind in the third column
Glossary file
| Term | Meaning | Kind |
|---|---|---|
| Guest | x | Actor |
| Room | y | Work Object |
When
the File glossary selects the kind column by index
Then
the kinds are read from that column
A work_object kind alias maps to the object kind happy-path
✓ passed 1ms
Given
a glossary whose Kind cell says work_object
Glossary file
| Term | Meaning | Kind |
|---|---|---|
| Room | y | work_object |
When
the File glossary parses the kind
Then
it normalizes to the Work Object kind
An unrecognised kind value is rejected validation
✓ passed 0ms
Given
a glossary whose Kind cell holds an unknown value
Glossary file
| Term | Meaning | Kind |
|---|---|---|
| Guest | x | Wizard |
When
the File glossary loads the file
Then
a PytestGivenError names the unrecognised kind
A missing glossary file is reported clearly validation
✓ passed 0ms
Given
a path to a file that does not exist
When
a File glossary is opened on that path
Then
a PytestGivenError reports the file is not found
A term cell with no alphanumeric characters is rejected validation
✓ passed 1ms
Given
a row whose Term cell has no id-able characters
Glossary file
| Term | Meaning |
|---|---|
| @#$ | some definition |
When
the File glossary loads the file
Then
a PytestGivenError is raised with file:line context
Conflicting duplicate rows are rejected validation
✓ passed 1ms
Given
two rows for one Term with different definitions
Glossary file
| Term | Meaning |
|---|---|
| Guest | First definition. |
| Guest | Second definition. |
When
the File glossary loads the file
Then
a PytestGivenError reports the conflicting rows
A blank description normalizes to undefined happy-path
✓ passed 1ms
Given
a row whose description cell is blank
Glossary file
| Term | Meaning |
|---|---|
| Guest |   |
When
the File glossary parses it
Then
the Term definition is None, i.e. Undefined
Identical duplicate rows collapse to one term happy-path
✓ passed 1ms
Given
two identical rows for the same Term
Glossary file
| Term | Meaning |
|---|---|
| Guest | A person booking. |
| Guest | A person booking. |
When
the File glossary parses them
Then
they collapse to a single Term
Calling FileGlossary looks up a known term happy-path
✓ passed 0ms
Given
a File glossary loaded from a Markdown file
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
a known Term is looked up by call
Then
a Deferred term is returned
FileGlossary is a closed vocabulary validation
✓ passed 1ms
Given
a File glossary loaded from a Markdown file
Glossary file
# Glossary

| Term | Meaning |
|------|---------|
| Guest  | A person booking. |
| Room   | A bookable room. |
| search | Look up options. |
When
an unknown name is called
Then
a PytestGivenError is raised
no new Term was created
Calling an actor names a distinct instance happy-path
✓ passed 0ms
Given
an Actor handle for Guest
When
the Actor is called with a name
Then
an Instance with a distinct display is returned
Calling a verb records an inflection of the same term happy-path
✓ passed 0ms
Given
a Verb handle for confirm
When
the Verb is called with a surface form
Then
an Inflection sharing the verb identity is returned
Registering an actor returns a typed handle happy-path
✓ passed 0ms
Given
an empty glossary
When
an Actor is registered with a definition
Then
a typed Actor handle with the Actor kind is returned
Re-registering a term with matching fields is idempotent happy-path
✓ passed 0ms
Given
an Actor already registered with a definition
When
the same name and definition are registered again
Then
both handles share the one Term
Re-registering a term with a different definition is rejected validation
✓ passed 0ms
Given
an Actor already registered with one definition
When
the name is registered again with a different definition
Then
a PytestGivenError reports the conflict with the prior registration
The same name cannot be two different kinds validation
✓ passed 0ms
Given
a name already registered as an Actor
When
the same name is registered as a Verb
Then
a PytestGivenError reports the conflict with the prior registration
Registering an actor captures its definition site happy-path
✓ passed 0ms
Given
a rootdir-aware glossary
When
an Actor is registered
Then
the Term records a Source link to this file
Calling the glossary declares a kindless term happy-path
✓ passed 0ms
Given
an empty glossary
When
a Term is declared by call, without a kind
Then
the Term is registered as Kindless
Subscript looks up an already-declared term happy-path
✓ passed 0ms
Given
a glossary with one declared Term
When
the name is looked up by subscript
Then
the returned Term is the declared one
Subscripting an unknown name raises with a hint validation
✓ passed 0ms
Given
a glossary with one declared Term
When
a near-miss name is subscripted
Then
a PytestGivenError is raised with a spelling hint
Term kinds are inferred from activity-slot positions happy-path
✓ passed 0ms
Given
a glossary of three Kindless Term entries
When
Kind inference runs over a Story
Then
they resolve to Actor, Verb, Work Object by slot
An actor slot anywhere wins over a noun slot elsewhere happy-path
✓ passed 0ms
Given
a Term that sits in a noun slot in one Story
When
the same Term also appears in an Actor slot
Then
its inferred kind is Actor
A term used in no story stays kindless happy-path
✓ passed 0ms
Given
a Term referenced by no Story
When
Kind inference runs with no stories
Then
the Term remains Kindless
A term in both a verb and a noun slot is a conflict validation
✓ passed 0ms
Given
a Kindless Term used in a verb slot and a noun slot
When
kind resolution runs over both stories
Then
a PytestGivenError names the conflicting term
A declared kind consistent with its slot is kept happy-path
✓ passed 0ms
Given
a glossary with explicitly declared Term kinds
When
Kind inference runs over a matching Story
Then
the declared kinds are verified and preserved
A declared verb in an actor slot is rejected validation
✓ passed 0ms
Given
a Term declared as a Verb
When
kind resolution places it in the Actor slot
Then
a PytestGivenError names the misplaced term
A term used as both verb and actor is a conflict validation
✓ passed 0ms
Given
a Kindless Term used in a verb slot and an actor slot
When
kind resolution runs over both stories
Then
a PytestGivenError names the conflicting term
A declared work object in an actor slot is rejected validation
✓ passed 0ms
Given
a Term declared as a Work Object
When
kind resolution places it in the Actor slot
Then
a PytestGivenError names the misplaced term
A declared actor in a verb slot is rejected validation
✓ passed 0ms
Given
a Term declared as an Actor
When
kind resolution places it at position 1 (the verb slot)
Then
a PytestGivenError says an actor cannot fill the verb slot
A conflict error names only the offending stories validation
✓ passed 0ms
Given
an Actor Term that also appears in a verb slot
When
kind resolution raises
Then
only the offending story is named in the message
A conflict message excludes stories with an unrelated slot validation
✓ passed 0ms
Given
a Kindless Term used in verb, actor and noun slots
When
the verb-vs-actor conflict is raised
Then
only the verb and actor stories are named, not the noun one
A declared verb in a noun slot is rejected validation
✓ passed 0ms
Given
a Term declared as a Verb
When
kind resolution places it at position ≥2 (a noun slot)
Then
a PytestGivenError says a verb cannot fill the noun slot
Slot positions alternate verb/noun after the actor happy-path
✓ passed 0ms
Given
the five positions of a short activity path
When
the Slot rule is applied to each position
Then
position 0 is the actor Slot, then verb and noun alternate
A pipe table parses into term and definition rows markdown happy-path
✓ passed 0ms
Given
a Markdown document with one pipe table
Markdown document
# Glossary

| Term | Meaning |
|------|---------|
| Guest | A person booking. |
| Room  | A bookable room. |
When
the parser reads it into rows for a File glossary
Then
each row carries a Term, definition and source line
Multiple tables in one file are merged markdown happy-path
✓ passed 0ms
Given
a document containing two separate pipe tables
Markdown document
# Glossary

| Term | Meaning |
|------|---------|
| Guest | A person booking. |
| Room  | A bookable room. |

## More

| Term | Meaning |
|---|---|
| Search | Look up. |
When
the parser reads the whole document
Then
every table contributes its Term rows
Columns can be selected by header name markdown happy-path
✓ passed 0ms
Given
a table with custom, differently-cased header names
Markdown document
| Word | Note | Role |
|---|---|---|
| Guest | x | Actor |
When
the parser selects columns by header name
Then
the named columns are matched case-insensitively
Escaped pipes are preserved in cells markdown happy-path
✓ passed 0ms
Given
cells containing escaped pipe characters (\|)
Markdown document
| Term | Meaning |
|---|---|
| A\|B | pipe\|here |
When
the parser splits the row
Then
the escaped pipe survives as a literal pipe
Tables inside fenced code blocks are skipped markdown happy-path
✓ passed 0ms
Given
a fenced code block that contains a look-alike table
Markdown document
```
| Term | Meaning |
|---|---|
| Fake | nope |
```

| Term | Meaning |
|---|---|
| Real | yes |
When
the parser reads the document
Then
only the real table outside the fence contributes rows
A file with no pipe table is rejected markdown validation
✓ passed 0ms
Given
a document with no pipe table
Markdown document
# Just a heading

No tables here.
When
the parser reads it for a File glossary
Then
no pipe table is reported
A missing named column is rejected markdown validation
✓ passed 0ms
Given
a Markdown document with one pipe table
Markdown document
# Glossary

| Term | Meaning |
|------|---------|
| Guest | A person booking. |
| Room  | A bookable room. |
When
the parser selects a header name that is absent
Then
a PytestGivenError names the missing column
A column index out of range is rejected markdown validation
✓ passed 0ms
Given
a Markdown document with one pipe table
Markdown document
# Glossary

| Term | Meaning |
|------|---------|
| Guest | A person booking. |
| Room  | A bookable room. |
When
the parser selects a column index past the table width
Then
a PytestGivenError names the out-of-range column
A data row with too few columns is rejected markdown validation
✓ passed 0ms
Given
a table with a data row narrower than its header
Markdown document
| Term | Meaning | Type |
|---|---|---|
| Guest | A person |
| Room | A bookable room | place |
When
the parser reads the short row
Then
a PytestGivenError points at the short row
Bold term cells render as clean terms markdown happy-path
✓ passed 0ms
Given
a Term cell written with **bold** emphasis
Markdown document
| Term | Meaning |
|---|---|
| **Scenario** | A decorated test. |
When
the parser reads the term cell
Then
the emphasis is unwrapped to the plain canonical
Italic and inline-code term cells are unwrapped markdown happy-path
✓ passed 0ms
Given
Term cells using *italic* and `code` emphasis
Markdown document
| Term | Meaning |
|---|---|
| *Step* | one. |
| `given` | two. |
When
the parser reads the term cells
Then
each unwraps to its plain text
Underscores inside an identifier survive markdown happy-path
✓ passed 0ms
Given
a Term literally named work_object
Markdown document
| Term | Meaning |
|---|---|
| work_object | a thing. |
When
the parser reads the term cell
Then
the single underscores are not treated as emphasis
Emphasis is stripped from kind cells too markdown happy-path
✓ passed 0ms
Given
a Kind cell written with bold emphasis
Markdown document
| Term | Meaning | Kind |
|---|---|---|
| Guest | x | **Actor** |
When
the parser reads the kind cell
Then
the kind is unwrapped to plain text
Definition markdown is left intact markdown happy-path
✓ passed 0ms
Given
a definition cell rich with inline code
Markdown document
| Term | Meaning |
|---|---|
| Scenario | A test decorated with `@scenario(...)`. |
When
the parser reads the row
Then
the definition keeps its markup for the tooltip
A pipe line without a separator is not a table markdown happy-path
✓ passed 0ms
Given
prose containing a stray pipe, then a real table
Markdown document
This line has a | in it but no separator follows.
Next line is not a separator.

| Term | Meaning |
|---|---|
| Real | yes |
When
the parser reads the document
Then
only the real pipe table produces rows
A step pairs its narration with a phase happy-path
✓ passed 0ms
When
a given Step descriptor is created
Then
it carries the given Phase and its Narration
when_then records the action and its outcome as siblings happy-path
✓ passed 0ms
Given
an Active scenario in a local Collector
When
a when_then block exits cleanly
Then
a when and a sibling then Step are recorded
when_then pairs with an inner pytest.raises happy-path
✓ passed 0ms
Given
an Active scenario in a local Collector
When
the when_then body raises and an inner pytest.raises swallows it
Then
both sibling steps are still recorded
when_then omits the then when the body raises uncaught validation
✓ passed 0ms
Given
an Active scenario in a local Collector
When
the when_then body raises with nothing catching inside
Then
only the when step is recorded — the outcome never held
A nested when becomes a child of the when_then action happy-path
✓ passed 0ms
Given
an Active scenario in a local Collector
When
a when opens inside the when_then body
Then
the sub-action is a child of the action and the then still follows
An actor handle in a path becomes a term ref story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
When
a Path is built from three glossary handles
Then
the Actor slot becomes a Term ref
An inflected verb keeps its term identity but shows the inflection story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
a Verb handle called with an Inflection
When
it takes the verb slot of a Path
Then
the Term ref shows the inflection over the same Verb
A bare string in a path becomes a connective word story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
When
a Path is built with a bare word between term nodes
Then
the bare word becomes an Activity Part word, not a Term ref
A path needs at least an actor, a verb and a node story-grammar validation
✓ passed 0ms
Given
a Guest actor
a search verb
When
a Path of only two parts is built
Then
a PytestGivenError rejects it as too short
Position 0 of a path must be an actor story-grammar validation
✓ passed 0ms
Given
a search verb
a Room work object
When
a Path is built with a Work Object in position 0
Then
a PytestGivenError says position 0 is the Actor slot
A verb cannot open a path story-grammar validation
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
When
a Verb is placed in position 0 of a Path
Then
a PytestGivenError says position 0 is the Actor slot
A bare string may stand in for the actor slot story-grammar happy-path
✓ passed 0ms
Given
a search verb
a Room work object
When
a bare string takes position 0 of a Path
Then
it is accepted as an Activity Part word
Position 1 of a path must be a verb story-grammar validation
✓ passed 0ms
Given
a Guest actor
a Room work object
When
an Actor is placed in position 1 of a Path
Then
a PytestGivenError says position 1 is the Verb slot
A work object cannot fill the verb slot story-grammar validation
✓ passed 0ms
Given
a Guest actor
a Room work object
When
a Work Object is placed in position 1 of a Path
Then
a PytestGivenError says position 1 is the Verb slot
Position 2 of a path must be a noun story-grammar validation
✓ passed 0ms
Given
a Guest actor
a search verb
When
a Verb is placed in position 2 of a Path
Then
a PytestGivenError says position 2 is the noun slot
A bare verb may sit between two real entity nodes story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a Room work object
When
a bare verb sits between an Actor and a Work Object
Then
the entities are term refs and the verb stays a bare word
A path may be fully bare words story-grammar happy-path
✓ passed 0ms
Given
three plain words with no glossary handles
When
a Path is built from them
Then
every part is an Activity Part word
Node/edge alternation allows a trailing connective node story-grammar happy-path
✓ passed 0ms
Given
an Actor, a Verb, a Work Object and a second actor
When
they form a five-part Path joined by a connective
Then
even positions are term-ref nodes and the connective stays a word
A path may not end on a dangling edge story-grammar validation
✓ passed 0ms
Given
an Actor, Verb and Work Object plus a connective
When
a path ending on a connective edge is built
Then
a PytestGivenError rejects the dangling edge
A single-path activity synthesizes one path story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
When
an Activity is built from handles directly
Then
it wraps a single Path
An activity may branch into multiple paths story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
two alternate Path branches
When
they are combined into one Activity
Then
the activity carries both paths
Mixing loose parts and prebuilt paths is rejected story-grammar validation
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
a prebuilt Path
When
it is combined with loose handles in one Activity
Then
a PytestGivenError rejects the mix
Activity id 0 is reserved story-grammar validation
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
When
an Activity is built with explicit id=0
Then
a PytestGivenError says id=0 is reserved
A story auto-numbers its activities from one story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
When
a Story is built from two Activity rows
Then
the activities are numbered 1 and 2
Auto-numbering skips ids already taken explicitly story-grammar happy-path
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
a mix of explicit and auto Activity ids
When
they are assembled into a Story
Then
auto picks skip the ids already used explicitly
Duplicate activity ids in a story are rejected story-grammar validation
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
two Activity rows sharing an explicit id
When
they are assembled into a Story
Then
a PytestGivenError reports the duplicate activity id
A story derives its id from its title story-grammar happy-path
✓ passed 0ms
Given
a human-readable story title
When
a Story is built from it
Then
its id is the slugified title
A story may span only one glossary story-grammar validation
✓ passed 0ms
Given
a Guest actor
a search verb
a Room work object
two activities that reach two different glossaries
When
a Story is built spanning both glossaries
Then
a PytestGivenError says a story spans multiple glossaries
Two stories with the same id collide story-grammar validation
✓ passed 0ms
Given
a Story already declared under an id
When
a second story is declared with the same slug
Then
a PytestGivenError reports the id was already declared
A path may chain a second verb-object pair story-grammar happy-path
✓ passed 0ms
Given
an Actor, two Verb and two Work Object handles
When
they form a five-node Path (actor verb object verb object)
Then
every slot is a Term ref, with no bare words
A Template parses a bare placeholder step-text parametrization
✓ passed 0ms
Given
a deferred Templatize template with one placeholder
When
the template is parsed
Then
it splits into literal and placeholder Narration parts
A Template substitutes parametrize values step-text parametrization
✓ passed 0ms
Given
a Templatize template referencing a Case column
When
a Parameter table value is substituted in
Then
the placeholder is filled with that value
A t-string interpolation becomes a value part step-text happy-path
✓ passed 0ms
Given
a t-string step with one interpolated value
When
the t-string is parsed at runtime
Then
the interpolation becomes a Narration value part
A t-string can interpolate an arbitrary expression step-text happy-path
✓ passed 0ms
Given
a t-string step interpolating a computed expression
When
the t-string is parsed
Then
the Value highlight part records the full expression
A glossary handle in a t-string emits a term ref step-text
✓ passed 0ms
Given
an Actor handle from the glossary
When
the handle is interpolated into a t-string step
Then
the step carries a Term ref pill for that Actor
A work object handle in a t-string emits a term ref step-text
✓ passed 0ms
Given
a Work Object handle from the glossary
When
it is interpolated into a t-string step
Then
the step carries a Term ref for that Work Object
A bare verb handle keeps its canonical display step-text
✓ passed 0ms
Given
a Verb handle used without an Inflection
When
it is interpolated into a t-string step
Then
the Term ref shows the canonical verb
An inflected verb in a t-string shows the inflection step-text
✓ passed 0ms
Given
a Verb handle called with an Inflection
When
it is interpolated into a t-string step
Then
the Term ref shows the inflection but keeps the verb id
A term ref may not carry a format spec step-text validation
✓ passed 0ms
Given
an Actor handle interpolated with a format spec
When
the t-string is parsed
Then
a PytestGivenError says a Term ref takes no format spec
A FileGlossary handle works in a t-string step step-text
✓ passed 0ms
Given
a Deferred term from a File glossary
When
it is interpolated into a t-string step
Then
the step carries a single Term ref pill
The glossary view aggregates instances and verb forms happy-path
✓ passed 0ms
Given
a Report whose Story and Scenario reference entity Instances and an Inflection
Report data
{
  "metadata": {
    "project": "p",
    "timestamp": "t",
    "pytest_version": "8",
    "plugin_version": "0",
    "commit_sha": null
  },
  "scenarios": [
    {
      "id": "t",
      "narration": {
        "text": "s",
        "parts": []
      },
      "module": "m",
      "tags": [],
      "status": "passed",
      "duration_ms": 0,
      "steps": [
        {
          "phase": "when",
          "narration": {
            "text": "x",
            "parts": [
              {
                "term_id": "guest",
                "display": "Alice",
                "expression": "",
                "param_column": null
              },
              {
                "term_id": "search",
                "display": "searches",
                "expression": "",
                "param_column": null
              },
              {
                "term_id": "room",
                "display": "Deluxe Suite",
                "expression": "",
                "param_column": null
              }
            ]
          },
          "status": "passed",
          "children": [],
          "attachments": [],
          "error": null,
          "activity_ids": [],
          "fixture_name": null
        }
      ],
      "parameters": null,
      "error": null,
      "skip_reason": null,
      "source": null,
      "story_id": "book",
      "activity_ids": []
    }
  ],
  "glossary": {
    "terms": [
      {
        "id": "guest",
        "kind": "actor",
        "canonical": "Guest",
        "definition": null,
        "source": null
      },
      {
        "id": "room",
        "kind": "object",
        "canonical": "Room",
        "definition": null,
        "source": null
      },
      {
        "id": "search",
        "kind": "verb",
        "canonical": "search",
        "definition": null,
        "source": null
      }
    ]
  },
  "stories": [
    {
      "id": "book",
      "title": "Book",
      "activities": [
        {
          "id": 1,
          "paths": [
            {
              "parts": [
                {
                  "term_id": "guest",
                  "display": "Alice"
                },
                {
                  "term_id": "search",
                  "display": "searches for"
                },
                {
                  "term_id": "room",
                  "display": "Deluxe Suite"
                }
              ]
            }
          ]
        }
      ],
      "source": null
    }
  ]
}
When
the Glossary aggregations are built
Then
the entity terms collect their Instances
the verb collects its Inflection but not its canonical form
Terms referenced by an activity record the story happy-path
✓ passed 0ms
Given
a Story whose Activity references an actor and a verb
When
the Glossary aggregations are built
Then
the actor and the verb each list that Story
A canonical entity reference is not an instance happy-path
✓ passed 0ms
Given
a Story activity and a Step referencing entities by canonical name only
When
the Glossary aggregations are built
Then
neither entity term records an Instance
A kindless term records only its story ref happy-path
✓ passed 0ms
Given
a Kindless Term referenced by a Story activity
When
the Glossary aggregations are built
Then
the Term lists the Story but no Instance and no Inflection
An instance seen in a fixture step records its fixture provenance happy-path
✓ passed 0ms
Given
a Scenario whose fixture-sourced Step names an Instance
When
the Glossary aggregations are built
Then
the Instance carries the fixture name
The term index maps each term to its scenarios once happy-path
✓ passed 0ms
Given
a Scenario referencing one Term in two steps and another in its name
When
the term-scenario index is built
Then
each Term maps to the scenario exactly once
An under-anchored activity is flagged ineligible in rollups happy-path
✓ passed 0ms
Given
a Story with an anchored and an under-anchored Activity
When
the story rollups are built
Then
only the anchored Activity is Coverage-eligible
A verb activity ref has one identity regardless of inflection happy-path
✓ passed 0ms
Given
a Verb written canonically and as an Inflection
When
Coverage derives each Term ref identity
Then
both collapse to the one canonical verb identity
A branching activity unions references across its paths happy-path
✓ passed 0ms
Given
an Activity that branches into two Path alternatives
When
Coverage collects the Activity references
Then
both Instance identities across the branches are present
An instance step ref adds a canonical fallback happy-path
✓ passed 0ms
Given
a Step referring to a named Instance
When
Coverage computes the identity set for the Step
Then
it includes the canonical Term ref fallback
A verb ref always resolves to its canonical identity happy-path
✓ passed 0ms
Given
a Step using an Inflection of a Verb
When
Coverage computes its identity set
Then
the identity ignores the surface form and stays canonical
An unknown term ref is skipped validation
✓ passed 0ms
Given
a Step referencing a Term not in the glossary
When
Coverage computes its identity set
Then
the unknown ref contributes nothing to the identity set
An instance step covers a canonical activity happy-path
✓ passed 0ms
Given
a Story with a canonical Activity
When
a Scenario step names a specific Instance
Then
Coverage reports the Activity as covered
A canonical step does not cover an instance activity happy-path
✓ passed 0ms
Given
an Activity anchored to a named Instance
When
a Scenario step only names the canonical Actor
Then
Coverage leaves the more specific instance activity uncovered
A scenario activity binding constrains coverage happy-path
✓ passed 0ms
Given
a Story with two matching activities
When
the Scenario binds only to activity 1
Then
Coverage considers only the bound Activity
An activity with two distinct terms is coverage-eligible happy-path
✓ passed 0ms
Given
an Activity anchored by two distinct Term refs
When
its Coverage eligibility is checked
Then
it is eligible for Coverage tracking
An under-anchored activity is not coverage-eligible happy-path
✓ passed 0ms
Given
an Activity that mentions only one distinct Term
When
its Coverage eligibility is checked
Then
it is ineligible — Coverage needs at least two anchors
An under-anchored activity is never reported as covered happy-path
✓ passed 0ms
Given
a Story whose Activity is all bare words
When
coverage is computed against a scenario
Then
Coverage excludes the under-anchored Activity
Nested steps are walked for coverage happy-path
✓ passed 0ms
Given
a Story with one canonical Activity
When
the covering Term refs live in a nested child Step
Then
the nested Step still counts and the Activity is covered
An explicit step binding covers an eligible activity happy-path
✓ passed 0ms
Given
a Story with a coverage-eligible Activity
When
a Step binds to it explicitly by id
Then
Coverage counts it directly, without identity matching
An explicit binding still requires eligibility validation
✓ passed 0ms
Given
a Story whose Activity is under-anchored
When
a Step binds to it explicitly by id
Then
eligibility gates the binding, so Coverage stays empty
Parameter coloring marks placeholders and table headers happy-path
✓ passed 60ms
Given
a Report holding a Parametrized scenario with a Parameter table
When
the Renderer renders the HTML page
Then
Parameter coloring classes mark the merged placeholder and the table headers
A passed scenario renders as a checked heading with step bullets happy-path
✓ passed 0ms
Given
a Report holding a passed Scenario with three steps
Scenario record
{
  "id": "tests/t.py::test_buy",
  "narration": {
    "text": "Buy coffee",
    "parts": []
  },
  "module": "tests/t.py",
  "tags": [
    "billing",
    "happy-path"
  ],
  "status": "passed",
  "duration_ms": 0,
  "steps": [
    {
      "phase": "given",
      "narration": {
        "text": "a machine",
        "parts": []
      },
      "status": "passed",
      "children": [],
      "attachments": [],
      "error": null,
      "activity_ids": [],
      "fixture_name": null
    },
    {
      "phase": "when",
      "narration": {
        "text": "I insert $2",
        "parts": []
      },
      "status": "passed",
      "children": [],
      "attachments": [],
      "error": null,
      "activity_ids": [],
      "fixture_name": null
    },
    {
      "phase": "then",
      "narration": {
        "text": "I get a coffee",
        "parts": []
      },
      "status": "passed",
      "children": [],
      "attachments": [],
      "error": null,
      "activity_ids": [],
      "fixture_name": null
    }
  ],
  "parameters": null,
  "error": null,
  "skip_reason": null,
  "source": null,
  "story_id": null,
  "activity_ids": []
}
When
the Markdown Report is rendered
Then
the heading is checked and each Step is a phase bullet
Nested steps indent under their parent happy-path
✓ passed 0ms
Given
a Scenario whose when Step has a nested child
When
the Markdown Report is rendered
Then
the child bullet indents under its parent
Structured narration renders terms, values and placeholders step-text
✓ passed 0ms
Given
a Step whose Narration carries a Term ref, a value and a placeholder
When
the Markdown Report is rendered
Then
the Term ref renders in guillemets, the value verbatim and the placeholder in braces
A parametrized scenario renders its parameter table parametrization
✓ passed 0ms
Given
a Parametrized scenario with a two-Case Parameter table
When
the Markdown Report is rendered
Then
the heading counts the cases and the Parameter table lists each row
A failing step is marked with a minimal error digest happy-path
✓ passed 0ms
Given
a failed Scenario whose then Step carries a two-line error and an internal frame
Error record
{
  "message": "ValueError: not sold out\nassert 1 == 0",
  "frames": [
    {
      "path": "/x/_pytest/runner.py",
      "lineno": 1,
      "func": "run",
      "code": "",
      "is_internal": true
    },
    {
      "path": "/x/tests/test_shop.py",
      "lineno": 88,
      "func": "test_sold_out",
      "code": "buy(m)",
      "is_internal": false
    }
  ],
  "error_tail": null
}
When
the Markdown Report is rendered
Then
the heading is crossed and the failed step is marked
only the first message line and the non-internal frame are quoted
A multi-line attachment renders as a fenced block happy-path
✓ passed 0ms
Given
a Step carrying a multi-line Attachment
When
the Markdown Report is rendered
Then
the Attachment content sits in an indented fence, not inline
A skipped scenario shows its skip reason happy-path
✓ passed 0ms
Given
a skipped Scenario with a reason
When
the Markdown Report is rendered
Then
the heading is marked skipped and the reason follows the node id
Grouping merges parametrize cases into one scenario parametrization
✓ passed 0ms
Given
three Case records of one Parametrized scenario
When
the grouping pass merges them
Then
one scenario remains and any failed Case fails it
Term ids are derived as URL-safe slugs happy-path
✓ passed 0ms
Given
the name {text}
When
it is slugified into a Term id
Then
the id is the expected slug {expected}
text expected status
Guest guest
Order received order-received
Work Object work-object
do_the_thing do-the-thing
Buy / sell buy-sell
Guest #1 guest-1
café caf
booking system booking-system
A name with no id-able characters is rejected validation
✓ passed 0ms
Given
the name {text}
When
it is slugified into a Term id
Then
a PytestGivenError reports the derived id is empty
text status
---
###
A cross-phase step cannot open inside a when_then body validation
✓ passed 0ms
Given
an Active scenario in a local Collector
When
a given or then opens inside the when_then body
Then
a PytestGivenError reports the cross-phase nesting
the Step stack is left balanced
phase_name status
given
then
A Template accepts bare identifiers only step-text validation
✓ passed 0ms
Given
the placeholder {text}
When
a Templatize template is built from it
Then
a PytestGivenError says bare identifiers only
text status
count={obj.attr}
{d[key]}
{x + 1}
Adopt pytest-given
11 activities · 19 scenarios
1
Domain ExperttellsStoryto theDeveloper
no coverage
2
DevelopercapturesStoryasActivity
3 scenarios
3
DeveloperbuildsGlossarywith theDomain Expert
3 scenarios
4
AgentwritesScenariowithTagagainst theGlossary
1 scenario
5
AgentnarratesStepwith aPhase
3 scenarios
6
AgentattachesAttachmentto aStep
1 scenario
7
CollectorrecordsStepon theStep stack
3 scenarios
8
CollectorgraftsFixture recordingfrom aStep fixture
3 scenarios
9
CollectorgroupsParametrized scenariointo aParameter table
1 scenario
10
RendererrendersReportwithParameter coloring
1 scenario
11
Domain ExpertreviewsScenarioin theReport
no coverage
Scenarios
Steps record with their phases
✓ passed
Covers: 7
Steps pushed during fixture setup record into the fixture recording
✓ passed
Covers: 7
An attachment lands on the step being recorded
✓ passed
Covers: 6
Fixture-body steps do not leak into the active scenario
✓ passed
Covers: 7
A fixture recording is deep-copied when grafted
✓ passed
Covers: 8
A leaf given is grafted as a childless given step
✓ passed
Covers: 8
Grafting with an override replaces the root label but keeps children
✓ passed
Covers: 8
Registering an actor returns a typed handle
✓ passed
Covers: 3
Re-registering a term with matching fields is idempotent
✓ passed
Covers: 3
Calling the glossary declares a kindless term
✓ passed
Covers: 3
A step pairs its narration with a phase
✓ passed
Covers: 5
when_then records the action and its outcome as siblings
✓ passed
Covers: 5
A nested when becomes a child of the when_then action
✓ passed
Covers: 5
A single-path activity synthesizes one path
✓ passed
Covers: 2
An activity may branch into multiple paths
✓ passed
Covers: 2
A story auto-numbers its activities from one
✓ passed
Covers: 2
A FileGlossary handle works in a t-string step
✓ passed
Covers: 4
Parameter coloring marks placeholders and table headers
✓ passed
Covers: 10
Grouping merges parametrize cases into one scenario
✓ passed
Covers: 9
Glossary
48 terms · 5 actors · 15 work objects · 2 verbs · 26 uncategorized
Actors 5 terms
Collector
The module-level singleton that accumulates scenarios, fixture recordings, and parameter info during a pytest session. Reset at the start of each session.
1 story · 7 scenarios
Renderer
Converts a JSON report into a self-contained HTML page.
1 story · 1 scenario
Developer
Person who writes the application code and — together with the Agent — the scenarios; curates the glossary with the Domain Expert.
1 story
Domain Expert
Person who owns the domain knowledge and the ubiquitous language; source of domain stories and reviewer of scenarios and reports. A stakeholder in the broad sense.
1 story
Agent
AI coding agent that authors and maintains scenarios alongside the Developer, guided by the pytest-given skills.
1 story
Work Objects 15 terms
Scenario
A test function decorated with @scenario(...). Not every pytest test is a scenario — undecorated tests are tolerated but not collected.
1 story · 11 scenarios
Step
A unit of narration: a with given(...) / when(...) / then(...) block, or the root recording from a step fixture. Steps nest. Each carries a phase, text, status ('passed' by default; set to 'failed' on the step where a scenario fails, for highlighting in the report), optional error, attachments, and children.
1 story · 20 scenarios
Phase
The category of a step: given, when, or then. A step has exactly one phase.
1 story · 2 scenarios
Tag
Free-form string label attached via @scenario(name, tags=[...]). Used by the report's filter UI.
1 story · 1 scenario
Attachment
A labeled blob (text or JSON) bound to the currently-active step via attach(label, content).
1 story · 2 scenarios
Parametrized scenario
A @scenario-decorated test that also carries @pytest.mark.parametrize(...). Produces multiple scenario records during a run; pytest-given groups them.
1 story · 3 scenarios
Parameter table
The per-scenario grouping of column names + cases. Appears in the report below the grouped-template steps.
1 story · 3 scenarios
Step fixture
A pytest fixture whose function is wrapped with @given(text). Only @given is allowed on fixtures; @when / @then are rejected.
1 story
Fixture recording
A captured subtree of steps + attachments produced while a step fixture is being set up (and, for generator fixtures, torn down). Stored keyed by fixture-instance identity.
1 story · 5 scenarios
Step stack
The chain of currently-open steps; entered by with given(...), popped on exit. Mirrored inside a fixture recording while a fixture body is running.
1 story · 1 scenario
Report
The output artifact: a JSON data file and optional self-contained HTML and Markdown renderings derived from it. The JSON is the source of truth.
1 story · 9 scenarios
Parameter coloring
Each parametrize column gets a stable highlight color; placeholders and matching values share that color wherever they appear in step text and the parameter table.
1 story · 1 scenario
Glossary
The Ubiquitous-Language concept — the shared vocabulary a domain speaks in — and the class that realizes it: Glossary(), with .actor(...), .work_object(...), .verb(...) registration methods and g('foo') (declare-or-get a kindless term, optional definition=) / g['foo'] (get-only, raises on unknown) accessor forms.
1 story · 5 scenarios
Story
A named flow modelled as a sequence of activities. Constructed by story('Title', [activity(...), ...]). Stories are first-class report tabs and the unit of coverage.
1 story · 21 scenarios
Activity
One row in a story — typically actor + verb + work_object plus optional connective words. Constructed by activity(...).
1 story · 20 scenarios
Verbs 2 terms
Group
Collapsing the N scenario records of a parametrized scenario into one logical scenario carrying a parameter table. Scenarios group when they share the same name and module.
1 story · 1 scenario
Graft
Attaching a fixture recording into the active scenario's step tree at the moment its host test starts.
1 story · 4 scenarios
Uncategorized 26 terms
Narration
The human-readable text on a step or scenario name. Modeled as a Narration dataclass bundling a flat rendered text: str with a parts: list[NarrationPart] (empty for plain-string authoring; populated when the source was a t-string or pytest_given.Template, with NarrationLiteral / NarrationValue / NarrationPlaceholder pieces). The structured form lets the templatizer and renderer treat parametrize-bound values specially without regex tricks.
5 scenarios
when_then
A step-authoring helper that emits a when action and its then outcome as two sibling steps from a single with block. Used mainly to narrate an expected raise (with when_then('the action', 'the error is raised'), pytest.raises(...)), so the action and its outcome stay distinct steps.
5 scenarios
Case
One row of a parametrized scenario — a single tuple of parameter values, its status, and any error.
3 scenarios
Templatize
Derive the grouped-template step text from the first case, so the report shows a single set of steps with {name} placeholders that the parameter table fills in per case. Non-first cases' structured text is discarded.
3 scenarios
Plain fixture
A pytest fixture without a pytest-given decorator. Used by tests but produces no step in the report.
Active scenario
The scenario currently being recorded into; tracked by node ID.
10 scenarios
Node ID
A pytest test identifier (e.g., tests/test_x.py::test_y[a-b]). Used as a key throughout the collector.
1 scenario
Value highlight
A neutral highlight applied to t-string interpolation values that don't correspond to a parametrize column (e.g., a computed expression like price * 1.2).
1 scenario
Source link
A clickable file:line anchor on a scenario card. Resolved from the given_source_link config (preset name like vscode / github, or a raw URL template). Captured per-scenario as a SourceLocation (POSIX relpath + 1-indexed line) from pytest.Item.location. Disabled by default.
1 scenario
File glossary
A glossary loaded from a Markdown file, via the FileGlossary(path) class. It parses all GFM pipe tables in the file into the same inner Glossary model; terms are accessed by name (g['Guest'], case-insensitive). Kind inference fills in term kinds post-collection from activity slot positions when no explicit kind_column is configured.
20 scenarios
Deferred term
A term obtained before its kind is known — the DeferredTermHandle returned by the code glossary (g('foo') declare-or-get, g['foo'] get-only) and by a file glossary (g['Guest']). One handle type serves all kinds (unlike the eager Actor/Work Object/Verb handles); the kind stays None until kind inference runs.
2 scenarios
Term
A registered glossary entry: an Actor, Work Object, Verb, or kindless term. Each carries an id (slug), a canonical name, a kind (None when kindless), and an optional definition (`str
39 scenarios
Actor
A glossary term for a participant in the domain (e.g., Guest). Renders with the actor pill style.
23 scenarios
Work Object
A glossary term for a thing acted on (e.g., Room, Booking). Renders with the work-object pill style.
10 scenarios
Verb
A glossary term for an action (e.g., book, confirm). Verbs accept inflections — calling book('books') records books as a surface form of the canonical book.
17 scenarios
Term ref
An occurrence of a term inside narration. Modelled as NarrationTermRef in step text and as ActivityTermRef inside activity prose.
16 scenarios
Instance
A named refinement of an Actor or Work Object (e.g., guest('Alice') is an instance of the Guest actor). Instances aggregate in the Glossary tab's refs block.
10 scenarios
Inflection
A surface form of a Verb other than its canonical name (e.g., searches for as an inflection of search). Reported under "Also used as:" in the Glossary.
8 scenarios
Activity Part
The two-variant union making up an activity's prose (ActivityPart = `ActivityTermRef
3 scenarios
Path
A branching segment inside a story — path(...) lets alternate activity sequences share a prefix.
17 scenarios
Slot
A position role in an activity path, from its node/edge alternation: position 0 is the actor slot, odd positions are verb slots, and even positions ≥ 2 are noun slots. Slots drive both path validation and kind inference.
1 scenario
Scenario↔activity binding
The link between a scenario (or step) and one or more story activities. Carried by @scenario(story=, activities=) and the activity= kwarg on given/when/then.
3 scenarios
Coverage
The "did this scenario touch that activity" relation. Computed by the A_refs ⊆ S rule: an activity is covered when its set of term references (as identities, with a canonical fallback) is a subset of a single step's term-reference identities — matching is per step, not against the union across steps. A step can also cover an activity explicitly via the activity= pin, regardless of its narration.
14 scenarios
Kind inference
The post-collection pass (resolve_glossary_kinds) that assigns each undeclared term a kind from the slot positions it occupies across all story activities; declared kinds are verified against observed positions instead. A term used in incompatible slots (or conflicting with its declared kind) raises. A term never referenced by any activity stays kindless.
4 scenarios
Kindless
A term with kind=None — left unset when kind inference finds no story slot to infer from (a term used only in t-string steps, never in an activity). Shown in the report's Uncategorized bucket.
9 scenarios
Undefined
A term with definition is None; surfaced by a badge and filter in the Glossary view. Orthogonal to kindless.
1 scenario